/*
健康检查组件
功能：定期检查服务健康状态，当服务状态变化时更新注册中心
作用：确保服务注册中心只包含健康可用的服务，提高系统可靠性
*/
package health

import (
	"context"             // 用于控制goroutine生命周期
	"fmt"                 // 用于字符串格式化
	"gameServer/log"      // 用于日志输出
	"gameServer/registry" // 导入服务注册中心包，用于服务注册/注销操作
	"net"                 // 用于TCP健康检查
	"net/http"            // 用于HTTP健康检查
	"sync"                // 用于并发控制
	"time"                // 用于定时检查
)

/* //* 健康检查器实现，定期检查服务健康状态并更新注册中心，支持自定义检查函数，确保系统只使用健康可用的服务 */

// 健康检查器接口
type Checker interface {
	// 启动健康检查
	Start() error
	// 停止健康检查
	Stop() error
	// 注册服务检查
	RegisterService(service *registry.ServiceInfo, checkfunc CheckFunc) error
	// 注销服务检查 移除特定服务的健康检查
	UnregisterService(serviceID string) error
}

// 健康检查函数类型
// 定义了健康检查函数的签名：接收服务信息，返回检查结果(健康状态, 错误信息)
// 允许用户自定义检查逻辑
type CheckFunc func(service *registry.ServiceInfo) (bool, error)

// 健康检查器实现
type HealthChecker struct {
	registry registry.Registry        // 服务注册中心实例，用于更新服务状态
	checks   map[string]*serviceCheck // 存储服务检查信息：key为服务ID，value为检查详情
	mu       sync.RWMutex             // 读写锁，确保并发安全访问checks映射
	interval time.Duration            // 健康检查的时间间隔（如5秒一次）
	ctx      context.Context          // 上下文，用于控制健康检查goroutine的生命周期
	cancel   context.CancelFunc       // 取消函数，用于停止健康检查
	wg       sync.WaitGroup           // 等待组，用于优雅关闭所有goroutine
}

// 服务检查信息
// 存储单个服务的健康检查相关信息
type serviceCheck struct {
	service   *registry.ServiceInfo // 服务信息
	checkFunc CheckFunc             // 该服务对应的健康检查函数
	healthy   bool                  // 当前健康状态：true为健康，false为不健康

}

// 创建健康检查器
//
// 参数：
//
//	registry: 服务注册中心实例，用于在服务状态变化时更新注册信息
//	interval: 健康检查的时间间隔
//
// 返回值：
//
//	*HealthChecker: 初始化后的健康检查器实例
func NewHealthChecker(registry registry.Registry, interval time.Duration) *HealthChecker {
	// 创建可取消的上下文，用于控制健康检查的生命周期
	ctx, cancel := context.WithCancel(context.Background())
	return &HealthChecker{
		registry: registry,
		checks:   make(map[string]*serviceCheck),
		interval: interval,
		ctx:      ctx,
		cancel:   cancel, // 绑定取消函数
	}
}

// 注册服务检查
//
// 功能：为指定服务添加健康检查逻辑
// 参数：
//
//	service: 要检查的服务信息
//	checkFunc: 用于检查该服务的函数
//
// 返回值：
//
//	error: 操作成功返回nil，失败返回错误信息
func (h *HealthChecker) RegisterService(service *registry.ServiceInfo, checkFunc CheckFunc) error {
	h.mu.Lock()
	defer h.mu.Unlock()
	// 将服务检查信息存入映射，使用服务ID作为键
	h.checks[service.ID] = &serviceCheck{
		service:   service,   // 服务信息
		checkFunc: checkFunc, // 检查函数
		healthy:   true,      // 初始假设健康
	}
	log.Infof("Health check registered for service: %s (%s)", service.Name, service.ID)
	return nil
}

// 注销服务检查
// 功能：移除指定服务的健康检查
//
// 参数：
//
//	serviceID: 要移除检查的服务ID
//
// 返回值：
//
//	error: 操作成功返回nil，失败返回错误信息
func (h *HealthChecker) UnregisterService(serviceID string) error {
	h.mu.Lock()
	defer h.mu.Unlock()
	delete(h.checks, serviceID)
	log.Infof("Health check unregistered for service: %s", serviceID)
	return nil
}

// 启动健康检查
func (h *HealthChecker) Start() error {
	log.Infof("Starting health checker with interval: %v", h.interval)
	h.wg.Add(1) // 增加等待组计数，跟踪健康检查goroutine
	// 启动goroutine执行定时检查，避免阻塞当前线程
	go h.runChecks()
	return nil
}

// 停止健康检查
//
// 功能：优雅停止健康检查，确保所有资源正确释放
//
// 返回值：
//
//	error: 操作成功返回nil，失败返回错误信息
func (h *HealthChecker) Stop() error {
	if h.cancel != nil {
		h.cancel() // 调用取消函数，通知健康检查goroutine退出
	}
	h.wg.Wait() // 等待健康检查goroutine完全退出 这里会阻塞 确保健康检查的 goroutine 完全停止后，Stop() 才会返回
	log.Info("Health checker stopped")
	return nil
}

// 运行健康检查
//
// 功能：循环执行定时健康检查，是健康检查的核心控制逻辑
func (h *HealthChecker) runChecks() {
	// 确保检查函数执行完了，才会结束协程
	defer h.wg.Done() // 函数退出时减少等待组计数（信号量？？？）
	// 创建定时器，按照指定间隔触发检查
	ticker := time.NewTicker(h.interval)
	defer ticker.Stop() // 函数退出时停止定时器，释放资源
	for {
		select {
		/*
					h.ctx 是通过 context.WithCancel(context.Background()) 创建的可取消上下文，对应的取消函数是 h.cancel。
			当调用 h.Stop() 方法时，会执行 h.cancel()，此时 h.ctx.Done() 通道会被关闭，case <-h.ctx.Done(): 分支会被触发，runChecks 函数中的循环退出，健康检查的 goroutine 结束。
		*/
		case <-h.ctx.Done(): // 收到取消信号，退出循环
			return
		case <-ticker.C: // 定时器触发，执行检查
			h.performChecks()
		}
	}
}

// 执行健康检查
//
// 功能：对所有注册的服务执行健康检查
func (h *HealthChecker) performChecks() {
	h.mu.RLock() // 加读锁，允许并发读取检查列表
	// 创建检查列表副本，避免在检查过程中受注册/注销操作影响
	checks := make([]*serviceCheck, 0, len(h.checks))
	// 遍历所有可以检测的服务
	for _, check := range h.checks {
		checks = append(checks, check)
	}
	h.mu.RUnlock()
	// 对每个服务执行健康检查
	for _, check := range checks {
		h.checkService(check)
	}
}

// 检查单个服务
//
// 功能：对单个服务执行健康检查，并在状态变化时更新注册中心
//
// 参数：
//
//	check: 服务检查信息
func (h *HealthChecker) checkService(check *serviceCheck) {
	// 调用该服务的健康检查函数，获取健康状态（看起来每个服务的检测函数应该是单独的）
	healthy, err := check.checkFunc(check.service)
	if err != nil {
		log.Errorf("Health check failed for service %s: %v", check.service.ID, err)
	}
	h.mu.Lock() // 加写锁，确保更新状态时的并发安全
	defer h.mu.Unlock()
	// 检查服务是否仍在检查列表中（可能已被注销）
	if currentCheck, exists := h.checks[check.service.ID]; exists {
		// 只有当健康状态发生变化时才执行操作（在列表中）
		if healthy != currentCheck.healthy {
			currentCheck.healthy = healthy // 更新健康状态
			if healthy {
				// 服务从不健康变为健康：重新注册到注册中心
				log.Infof("Service %s is now healthy", check.service.ID)
				// 服务恢复健康，可以重新注册到注册中心
				h.registry.Register(check.service)
			} else {
				// 服务从健康变为不健康：从注册中心注销
				log.Infof("Service %s is now unhealthy", check.service.ID)
				// 服务不健康，从注册中心移除
				h.registry.Deregister(check.service.ID)
			}
		}
	}
}

// 默认HTTP健康检查函数
//
// 功能：通过HTTP请求检查服务健康状态
//
// 参数：
//
//	service: 要检查的服务信息
//
// 返回值：
//
//	bool: 服务是否健康
//	error: 检查过程中的错误信息
func DefaultHttpCheck(service *registry.ServiceInfo) (bool, error) {
	// 构建健康检查url，假设服务提供/health端点
	healthUrl := fmt.Sprintf("http://%s:%d/health", service.Address, service.Port)
	// 创建HTTP客户端，设置5秒超时 发送http请求
	client := &http.Client{Timeout: 5 * time.Second}
	// 发送GET请求到健康检查端点
	resp, err := client.Get(healthUrl)
	if err != nil {
		return false, err // 请求失败，服务不健康
	}
	/*
			HTTP 响应体（resp.Body） 是一个 io.ReadCloser 接口，本质上是一个网络流。当客户端发送 HTTP 请求并收到响应后，底层会维护一个与服务器的连接（通常是长连接），resp.Body 就是从这个连接中读取数据的流。
		defer resp.Body.Close()：通过 defer 确保在函数退出前关闭这个流，释放底层连接资源（将连接归还给连接池或关闭）。
		为什么必须做：如果不关闭 resp.Body，会导致连接资源被长期占用，最终耗尽客户端的连接池，无法再发起新的 HTTP 请求（出现 “连接耗尽” 错误）。
	*/
	defer resp.Body.Close() // 确保响应体被关闭，避免资源泄漏

	// 检查HTTP响应状态码是否为200 OK
	return resp.StatusCode == http.StatusOK, nil
}

// 默认TCP健康检查函数
//
// 功能：通过尝试建立TCP连接检查服务健康状态
//
// 参数：
//
//	service: 要检查的服务信息
//
// 返回值：
//
//	bool: 服务是否健康
//	error: 检查过程中的错误信息
func DefaultTcpCheck(service *registry.ServiceInfo) (bool, error) {
	// 尝试建立tcp链接
	addr := fmt.Sprintf("%s:%d", service.Address, service.Port)
	// 尝试建立TCP连接，设置5秒超时
	conn, err := net.DialTimeout("tcp", addr, 5*time.Second)
	if err != nil {
		return false, err // 连接失败，服务不健康
	}
	/*
			conn 是一个 TCP 连接对象，通过 net.DialTimeout 与服务端建立 TCP 连接后生成。TCP 连接是宝贵的系统资源，每个连接会占用本地端口和文件描述符。
		defer conn.Close()：通过 defer 确保在函数退出前关闭这个 TCP 连接，释放占用的端口和文件描述符。
		为什么必须做：如果不关闭 TCP 连接，会导致系统资源被耗尽（如达到最大文件描述符限制），后续无法再建立新的连接，表现为 “连接超时” 或 “无法创建连接” 错误。
	*/
	// 这里就是做一个检测，不进行具体收发，因此这样就可以了
	defer conn.Close() // 确保连接被关闭，避免资源泄漏
	return true, nil
}
